#include "pch.h"

#include "CCGen.h"
#include "CJobDispatcher.h"
#include "half.hpp"
#include "CCGenGpu.h"

#include <cassert>

//#define INTERLOCKED_INC(x) {InterlockedIncrement(&x);}

using namespace tbb::flow;

Vec4 gFace2DMapping[6][3];
// = {
// 	//XPOS face
// 	{ { 0, 0, -1, 0 },   //u towards negative Z
// 	{ 0, -1, 0, 0 },   //v towards negative Y
// 	{ 1, 0, 0, 0 } },  //pos X axis  
// 	//XNEG face
// 	{ { 0, 0, 1, 0 },   //u towards positive Z
// 	{ 0, -1, 0, 0 },   //v towards negative Y
// 	{ -1, 0, 0, 0 } },  //neg X axis       
// 	//YPOS face
// 	{ { 1, 0, 0, 0 },     //u towards positive X
// 	{ 0, 0, 1, 0 },     //v towards positive Z
// 	{ 0, 1, 0, 0 } },   //pos Y axis  
// 	//YNEG face
// 	{ { 1, 0, 0, 0 },     //u towards positive X
// 	{ 0, 0, -1, 0 },   //v towards negative Z
// 	{ 0, -1, 0, 0 } },  //neg Y axis  
// 	//ZPOS face
// 	{ { 1, 0, 0, 0 },     //u towards positive X
// 	{ 0, -1, 0, 0 },    //v towards negative Y
// 	{ 0, 0, 1, 0 } },   //pos Z axis  
// 	//ZNEG face
// 	{ { -1, 0, 0, 0 },    //u towards negative X
// 	{ 0, -1, 0, 0 },    //v towards negative Y
// 	{ 0, 0, -1, 0 } },   //neg Z axis  
// };

struct CPCubeMapNeighbor
{
	unsigned __int8 m_Face;    //index of neighboring face
	unsigned __int8 m_Edge;    //edge in neighboring face that abuts this face
};

CPCubeMapNeighbor sg_CubeNgh[6][4] =
{
	//XPOS face
	{ { CP_FACE_Z_POS, CP_EDGE_RIGHT },
	{ CP_FACE_Z_NEG, CP_EDGE_LEFT },
	{ CP_FACE_Y_POS, CP_EDGE_RIGHT },
	{ CP_FACE_Y_NEG, CP_EDGE_RIGHT } },
	//XNEG face
	{ { CP_FACE_Z_NEG, CP_EDGE_RIGHT },
	{ CP_FACE_Z_POS, CP_EDGE_LEFT },
	{ CP_FACE_Y_POS, CP_EDGE_LEFT },
	{ CP_FACE_Y_NEG, CP_EDGE_LEFT } },
	//YPOS face
	{ { CP_FACE_X_NEG, CP_EDGE_TOP },
	{ CP_FACE_X_POS, CP_EDGE_TOP },
	{ CP_FACE_Z_NEG, CP_EDGE_TOP },
	{ CP_FACE_Z_POS, CP_EDGE_TOP } },
	//YNEG face
	{ { CP_FACE_X_NEG, CP_EDGE_BOTTOM },
	{ CP_FACE_X_POS, CP_EDGE_BOTTOM },
	{ CP_FACE_Z_POS, CP_EDGE_BOTTOM },
	{ CP_FACE_Z_NEG, CP_EDGE_BOTTOM } },
	//ZPOS face
	{ { CP_FACE_X_NEG, CP_EDGE_RIGHT },
	{ CP_FACE_X_POS, CP_EDGE_LEFT },
	{ CP_FACE_Y_POS, CP_EDGE_BOTTOM },
	{ CP_FACE_Y_NEG, CP_EDGE_TOP } },
	//ZNEG face
	{ { CP_FACE_X_POS, CP_EDGE_RIGHT },
	{ CP_FACE_X_NEG, CP_EDGE_LEFT },
	{ CP_FACE_Y_POS, CP_EDGE_TOP },
	{ CP_FACE_Y_NEG, CP_EDGE_BOTTOM } }
};

bool CCGen::Init()
{   
	dispatcher->Init();

	gFace2DMapping[0][0] = Vec4(0, 0, -1, 0);
	gFace2DMapping[0][1] = Vec4(0, -1, 0, 0);
	gFace2DMapping[0][2] = Vec4(1, 0, 0, 0 );

	gFace2DMapping[1][0] = Vec4(0, 0, 1, 0);
	gFace2DMapping[1][1] = Vec4( 0, -1, 0, 0);
	gFace2DMapping[1][2] = Vec4( -1, 0, 0, 0);

	gFace2DMapping[2][0] = Vec4(1, 0, 0, 0);
	gFace2DMapping[2][1] = Vec4(0, 0, 1, 0);
	gFace2DMapping[2][2] = Vec4(0, 1, 0, 0);

	gFace2DMapping[3][0] = Vec4(1, 0, 0, 0);
	gFace2DMapping[3][1] = Vec4(0, 0, -1, 0);
	gFace2DMapping[3][2] = Vec4(0, -1, 0, 0);

	gFace2DMapping[4][0] = Vec4(1, 0, 0, 0);
	gFace2DMapping[4][1] = Vec4(0, -1, 0, 0);
	gFace2DMapping[4][2] = Vec4(0, 0, 1, 0);

	gFace2DMapping[5][0] = Vec4(-1, 0, 0, 0);
	gFace2DMapping[5][1] = Vec4(0, -1, 0, 0);
	gFace2DMapping[5][2] = Vec4(0, 0, -1, 0);

	return true;
}

void CCGen::AddFilterJob(const SFilterJob *newJob)
{
	if (dispatcher->CheckFilterJob(newJob))
	{
		if(*newJob->outputCubemap == nullptr)
		{
			SCubeMap *out_cubemap = new SCubeMap;

			const SFilterStateData *stateDesc = dispatcher->GetFilterStateDesc(newJob->filterState);

			unsigned int mipmap_count = (unsigned int)CGen::log2((float)stateDesc->desc.dstSize) + 1;
			unsigned int face_count = mipmap_count * 6;

			out_cubemap->height = stateDesc->desc.dstSize;
			out_cubemap->width = stateDesc->desc.dstSize;
			out_cubemap->pixelFormat = stateDesc->desc.dstPixelFormat;
			out_cubemap->mipCount = mipmap_count;

			out_cubemap->faces = new SCubeFace[face_count];

			for(unsigned int i_face = 0, i_surface = 0; i_face < 6; i_face++)
			{
				unsigned int mip_size = stateDesc->desc.dstSize;

				for (unsigned int i_level = 0; i_level < mipmap_count; i_level++)
				{
					SCubeFace *cur_face = &out_cubemap->faces[i_surface];

					cur_face->width = mip_size;
					cur_face->height = mip_size;

					int pixel_size = epf_pixel_size(stateDesc->desc.dstPixelFormat);
					int surface_size = pixel_size * mip_size * mip_size;

					cur_face->dataPtr = _aligned_malloc(surface_size, 16);
					cur_face->dataSize = surface_size;

					mip_size = mip_size >> 1;
					i_surface++;
				}
			}

			*newJob->outputCubemap = out_cubemap;
		}		

		dispatcher->DispatchNewJob(newJob);
	}
}

bool CCGen::EnableGpu()
{
	return dispatcher->EnableGpu();
}

CCGen::CCGen()
{
	dispatcher = new CJobDispatcher;
}

void CCGen::WaitForAll()
{
	dispatcher->WaitForAll();
}

bool CCGen::CreateFilterState( const SFilterStateDesc* stateDesc, SFilterState** outState )
{
	return dispatcher->CreateFilterState(stateDesc, outState);
}

bool CCGen::IsGpuEnabled()
{
	return dispatcher->IsGpuEnabled();
}

CCGen::~CCGen()
{
	SAFE_DELETE(dispatcher);
}

STapdirLUT * CJobDispatcher::GetTapDirLUT(const SFilterState* filterState)
{
	tbb::reader_writer_lock::scoped_lock_read stateLock(stateMutex);
	STapdirLUT *tapDirLut = tapdirLuts[filterState->GetStateID()];
	
	return tapDirLut;
}

void CJobDispatcher::Init()
{
	BuildGraph();
}

void CJobDispatcher::DispatchNewJob(const SFilterJob* new_job)
{
#ifdef USE_GPU
	if(gpuHandler->IsGpuEnabled())
	{
		CMatchLUTFunctor matchLUTs(this);

		SFilterJobInternal *internalJob = new SFilterJobInternal;

		internalJob->internalCounter = (*new_job->outputCubemap)->mipCount * 6;
		internalJob->job = *new_job;
		internalJob->vectorCubemap = new SCubeMap;
		matchLUTs(internalJob);

		AddActiveJob(internalJob);
		//All luts are ready here
	}
	else
#endif // USE_GPU
	{
		SFilterJobInternal *internalJob = new SFilterJobInternal;

		internalJob->internalCounter = (*new_job->outputCubemap)->mipCount * 6;
		internalJob->job = *new_job;
		internalJob->vectorCubemap = new SCubeMap;
		filterJobBufferNode->try_put(internalJob);

		AddActiveJob(internalJob);
	}

	InterlockedIncrement(&totalJobCount); //Not necessary
}

void CJobDispatcher::DispatchFilterSurfaceJob(const SFilterSurfaceJob *new_job)
{
	filterLevelJobBufferNode->try_put(*new_job);
}

SFilterExtentsLUT * CJobDispatcher::GetFilterExtentsLUT(const SFilterState* filterState)
{
	tbb::reader_writer_lock::scoped_lock_read stateLock(stateMutex);
	return filterExtentsLuts[filterState->GetStateID()];
}

bool CJobDispatcher::CreateFilterState(const SFilterStateDesc* stateDesc, SFilterState** outState)
{
	tbb::reader_writer_lock::scoped_lock lockState(stateMutex);

	int newStateIndex = filterStates.size();

	*outState = new SFilterState(newStateIndex);

	SFilterStateData *newInstance = new SFilterStateData;

	int mipCount = (int)CGen::log2((float)stateDesc->dstSize) + 1;

	assert(mipCount < MAX_CUBEMAP_MIPLEVEL);
	
	float RefSpecularPower = (
		stateDesc->lightingModel == CP_LIGHTINGMODEL_BLINN || 
		stateDesc->lightingModel == CP_LIGHTINGMODEL_BLINN_BRDF) ? stateDesc->specularPower / 4.0f : stateDesc->specularPower;

	float specularPower = RefSpecularPower;

	//Simply build pow tables here. Not an expensive operation.
	float **newPowTable = new float *[MAX_CUBEMAP_MIPLEVEL];

	for (int i_mip = 0; i_mip < mipCount; i_mip++)
	{
		if(stateDesc->excludeBase && i_mip == 0)
		{
			specularPower = 10000.0f;
		}
		else
		{
			if ( stateDesc->mipMode == CP_COSINEPOWER_CHAIN_MIPMAP)
			{
				float mipToCalculate = (float)(stateDesc->excludeBase ? i_mip - 1 : i_mip);
				float Glossiness = (mipCount == 1) ? 1.0f : CGen::max(1.0f - mipToCalculate / (float)(mipCount - 1), 0.0f);
				// This function must match the decompression function of the engine.
				specularPower = powf(2.0f, stateDesc->glossScale * Glossiness + stateDesc->glossBias);
				// take count of the lighting model
				specularPower = (stateDesc->lightingModel == CP_LIGHTINGMODEL_BLINN || stateDesc->lightingModel == CP_LIGHTINGMODEL_BLINN_BRDF) ? specularPower / 4.0f : specularPower;
			}
		}

		float srcTexelAngle;
		float dotProdThresh;
		int   filterSize;
		float filterAngle;
		float coneAngle;

		coneAngle = CCgenUtil::GetBaseFilterAngle(specularPower);

		//min angle a src texel can cover (in degrees)
		srcTexelAngle = (180.0f / CP_PIF) * atan2f(1.0f, (float)stateDesc->srcSize);

		//filter angle is 1/2 the cone angle
		filterAngle = coneAngle / 2.0f;

		//ensure filter angle is larger than a texel
		if (filterAngle < srcTexelAngle)
		{
			filterAngle = srcTexelAngle;
		}

		//ensure filter cone is always smaller than the hemisphere
		if (filterAngle > 90.0f)
		{
			filterAngle = 90.0f;
		}

		//the maximum number of texels in 1D the filter cone angle will cover
		//  used to determine bounding box size for filter extents
		filterSize = (int)ceil(filterAngle / srcTexelAngle);

		//ensure conservative region always covers at least one texel
		if (filterSize < 1)
		{
			filterSize = 1;
		}

		//dotProdThresh threshold based on cone angle to determine whether or not taps 
		// reside within the cone angle
		dotProdThresh = cosf((CP_PIF / 180.0f) * filterAngle);

		newInstance->filterSizes[i_mip] = filterSize;
		newInstance->dotpThreshold[i_mip] = dotProdThresh;
		newInstance->specularPowers[i_mip] = specularPower;

		//compute pow table
		static const int POW_TABLE_SIZE = 16 * 1024;

		newPowTable[i_mip] = (float*)_aligned_malloc(sizeof(float) * POW_TABLE_SIZE, 16);

		//SIMD area
		float dotpVal = 0.0f;
		float dS = 1.0f / POW_TABLE_SIZE;
		float powExponent =	specularPower;

		for (int i = 0; i < POW_TABLE_SIZE; i++)
		{
			newPowTable[i_mip][i] = pow(dotpVal, powExponent);

			dotpVal = dotpVal + dS;
		}

		powTables.push_back(newPowTable);

		// Decrease the specular power to generate the mipmap chain
		if (stateDesc->mipMode == CP_COSINEPOWER_CHAIN_DROP)
		{
			if(stateDesc->excludeBase)
			{
				specularPower = RefSpecularPower;
			}
			else
			{
				specularPower *= stateDesc->cosPowerDrop;
			}
		}
	}
	
	newInstance->desc = *stateDesc;	

	if (stateDesc->srcPixelFormat == ePF_R8G8BA8)
	{
		newInstance->desc.srcPixelFormat = ePF_R8G8BA8_UINT;//Will be converted to uint format during vectorization
	}

	filterStates.push_back(newInstance);
	tapdirLuts.push_back(new STapdirLUT);
	filterExtentsLuts.push_back(new SFilterExtentsLUT);
	
	assert(filterStates.size() == tapdirLuts.size());

	return true;
}

void CJobDispatcher::ComputeTapDirLUT(const SFilterState* filterState)
{
	assert(filterState->GetStateID() != -1);
#ifdef USE_GPU
	if(gpuHandler->IsGpuEnabled())
	{
		STapdirLUT *lut = GetTapDirLUT(filterState);
		lut->status = eLUTS_in_progress;

		CComputeTapDirLUTFunctor computeTapDir(this);

		computeTapDir(*filterState);

		lut->status = eLUTS_ready;
	}
	else
#endif
	{
		STapdirLUT *lut = GetTapDirLUT(filterState);
		if (lut->status.compare_and_swap(eLUTS_in_progress, eLUTS_invalid) == eLUTS_invalid)
		{
			computeTapDirLUTBufferNode->try_put(*filterState);
		}
	}	
}

void CJobDispatcher::ComputeFilterExtentsLUT(const SFilterState* filterState)
{
	assert(filterState->GetStateID() != -1);
#ifdef USE_GPU
	if(gpuHandler->IsGpuEnabled())
	{
		SFilterExtentsLUT *lut = GetFilterExtentsLUT(filterState);
		lut->status = eLUTS_in_progress;

		CComputeFilterExtentsLUTFunctor computeFilterExtents(this);

		computeFilterExtents(*filterState);

		lut->status = eLUTS_ready;
	}
	else
#endif
	{
		SFilterExtentsLUT *lut = GetFilterExtentsLUT(filterState);
		if (lut->status.compare_and_swap(eLUTS_in_progress, eLUTS_invalid) == eLUTS_invalid)
		{
			computeFilterExtentsLUTBufferNode->try_put(*filterState);
		}
	}
}

void CJobDispatcher::VectorizeCubemap(SFilterJobInternal *job)
{
#ifdef USE_GPU
	if(gpuHandler->IsGpuEnabled())
	{
		job->vectorCubemap->internalStatus = eLUTS_in_progress;

		CVectorizeCubemapFunctor vectorizeCubeMap(this);

		vectorizeCubeMap(job);
	}
	else
#endif
	{
		if (job->vectorCubemap->internalStatus.compare_and_swap(eLUTS_in_progress, eLUTS_invalid) == eLUTS_invalid)
		{
			vectorizeCubemapBufferNode->try_put(job);
		}
	}
}

bool CJobDispatcher::CheckFilterJob(const SFilterJob *job)
{
	if (job->filterState->GetStateID() == -1)
	{
		return false;
	}

	const SFilterStateData *filterState = GetFilterStateDesc(job->filterState);

	if (job->inputCubemap->width != job->inputCubemap->height || job->inputCubemap->width != filterState->desc.srcSize)
	{
		return false;
	}

// 	if (job->outputCubemap->width != job->outputCubemap->height || job->outputCubemap->width != filterState->desc.dstSize)
// 	{
// 		return false;
// 	}

	return true;
}

bool CJobDispatcher::EnableGpu()
{
#ifdef USE_GPU
	bool result = gpuHandler->EnableGpu();
	
	BuildGraph();

	return result;
#else
	return false;
#endif
}

void CJobDispatcher::BuildGraph()
{
#ifdef USE_GPU
	if(gpuHandler->IsGpuEnabled())
	{
		//assert(false);

		if(filterGraph)
		{
			filterGraph->wait_for_all();
			remove_edge(*filterJobBufferNode, *matchWithLUTNode);
			remove_edge(*computeTapDirLUTBufferNode, *computeTapDirLUTNode);
			remove_edge(*computeFilterExtentsLUTBufferNode, *computeFilterExtentsLUTNode);
			remove_edge(*filterLevelJobBufferNode, *processFilterJobNode);
			remove_edge(*vectorizeCubemapBufferNode, *vectorizeCubemapNode);
			SAFE_DELETE(filterJobBufferNode);
			SAFE_DELETE(matchWithLUTNode);
			SAFE_DELETE(computeTapDirLUTBufferNode);
			SAFE_DELETE(computeFilterExtentsLUTBufferNode);
			SAFE_DELETE(vectorizeCubemapBufferNode);
			SAFE_DELETE(vectorizeCubemapNode);
			SAFE_DELETE(computeTapDirLUTNode);
			SAFE_DELETE(computeFilterExtentsLUTNode);
			SAFE_DELETE(filterLevelJobBufferNode);
			SAFE_DELETE(processFilterJobNode);
			SAFE_DELETE(filterGraph);
		}

	}
	else
#endif
	{
		filterGraph = new tbb::flow::graph();

		filterJobBufferNode = new buffer_node<SFilterJobInternal*>(*filterGraph);

		matchWithLUTNode = new function_node<SFilterJobInternal*>(*filterGraph, tbb::flow::serial, CJobDispatcher::CMatchLUTFunctor(this));

		computeTapDirLUTBufferNode = new buffer_node<SFilterState>(*filterGraph);

		computeFilterExtentsLUTBufferNode = new buffer_node<SFilterState>(*filterGraph);

		vectorizeCubemapBufferNode = new tbb::flow::buffer_node<SFilterJobInternal*>(*filterGraph);

		vectorizeCubemapNode = new tbb::flow::function_node<SFilterJobInternal*>(*filterGraph, tbb::flow::serial, CJobDispatcher::CVectorizeCubemapFunctor(this));

		computeTapDirLUTNode = new function_node<SFilterState>(*filterGraph, 2, CJobDispatcher::CComputeTapDirLUTFunctor(this));

		computeFilterExtentsLUTNode = new function_node<SFilterState>(*filterGraph, 2, CJobDispatcher::CComputeFilterExtentsLUTFunctor(this));

		filterLevelJobBufferNode = new buffer_node<SFilterSurfaceJob>(*filterGraph);

		processFilterJobNode = new function_node<SFilterSurfaceJob>(*filterGraph, 8, CJobDispatcher::CProcessFilterJobFunctor(this));

		make_edge(*filterJobBufferNode, *matchWithLUTNode);

		make_edge(*computeTapDirLUTBufferNode, *computeTapDirLUTNode);

		make_edge(*computeFilterExtentsLUTBufferNode, *computeFilterExtentsLUTNode);

		make_edge(*filterLevelJobBufferNode, *processFilterJobNode);

		make_edge(*vectorizeCubemapBufferNode, *vectorizeCubemapNode);
	}
}

void CCgenUtil::TexelCoordToVect(unsigned int face, float a_U, float a_V, int a_Size, Vec4 &outVec, EFixupType fixupType)
{
	float nvcU, nvcV;

	static const int CP_UDIR = 0;
	static const int CP_VDIR = 1;
	static const int CP_FACEAXIS = 2;

	if (fixupType == CP_FIXUP_STRETCH && a_Size > 1)
	{
		nvcU = (2.0f * (float)a_U / ((float)a_Size - 1.0f)) - 1.0f;
		nvcV = (2.0f * (float)a_V / ((float)a_Size - 1.0f)) - 1.0f;
	}
	else
	{
		// Change from original AMD code
		// transform from [0..res - 1] to [- (1 - 1 / res) .. (1 - 1 / res)]
		// + 0.5f is for texel center addressing
		nvcU = (2.0f * ((float)a_U + 0.5f) / (float)a_Size) - 1.0f;
		nvcV = (2.0f * ((float)a_V + 0.5f) / (float)a_Size) - 1.0f;
	}

	if (a_Size > 1)
	{
		if (fixupType == CP_FIXUP_WARP)
		{
			float a = powf(float(a_Size), 2.0f) / powf(float(a_Size - 1), 3.0f);
			nvcU = a * powf(nvcU, 3) + nvcU;
			nvcV = a * powf(nvcV, 3) + nvcV;

			Vec4 scaled_vec1;
			Vec4 scaled_vec2;

			CGen::Vec4Scale(&scaled_vec1, &gFace2DMapping[face][CP_UDIR], nvcU);
			CGen::Vec4Scale(&scaled_vec2, &gFace2DMapping[face][CP_VDIR], nvcV);
			
			CGen::Vec4Add(&outVec, &scaled_vec1, &scaled_vec2);
			CGen::Vec4Add(&outVec, &outVec, &gFace2DMapping[face][CP_FACEAXIS]);

			CGen::Vec4Normalize(&outVec, &outVec);
		}
		else
		{
			assert(false);
		}
	}
	else
	{
		Vec4 scaled_vec1;
		Vec4 scaled_vec2;

		CGen::Vec4Scale(&scaled_vec1, &gFace2DMapping[face][CP_UDIR], nvcU);
		CGen::Vec4Scale(&scaled_vec2, &gFace2DMapping[face][CP_VDIR], nvcV);

		CGen::Vec4Add(&outVec, &scaled_vec1, &scaled_vec2);
		CGen::Vec4Add(&outVec, &outVec, &gFace2DMapping[face][CP_FACEAXIS]);
		CGen::Vec4Normalize(&outVec, &outVec);
	}
	// 				else if (a_FixupType == CP_FIXUP_BENT && a_Size > 1)//TODO
	// 				{
	// 					// Method following description of Physically based rendering slides from CEDEC2011 of TriAce
	// 
	// 					// Get vector at edge
	// 					float EdgeNormalU[3];
	// 					float EdgeNormalV[3];
	// 					float EdgeNormal[3];
	// 					float EdgeNormalMinusOne[3];
	// 
	// 					// Recover vector at edge
	// 					//U contribution
	// 					VM_SCALE3(EdgeNormalU, sgFace2DMapping[a_FaceIdx][CP_UDIR], nvcU < 0.0 ? -1.0f : 1.0f);
	// 					//V contribution
	// 					VM_SCALE3(EdgeNormalV, sgFace2DMapping[a_FaceIdx][CP_VDIR], nvcV < 0.0 ? -1.0f : 1.0f);
	// 					VM_ADD3(EdgeNormal, EdgeNormalV, EdgeNormalU);
	// 					//add face axis
	// 					VM_ADD3(EdgeNormal, sgFace2DMapping[a_FaceIdx][CP_FACEAXIS], EdgeNormal);
	// 					//normalize vector
	// 					VM_NORM3(EdgeNormal, EdgeNormal);
	// 
	// 					// Get vector at (edge - 1)
	// 					float nvcUEdgeMinus1 = (2.0f * ((float)(nvcU < 0.0f ? 0 : a_Size - 1) + 0.5f) / (float)a_Size) - 1.0f;
	// 					float nvcVEdgeMinus1 = (2.0f * ((float)(nvcV < 0.0f ? 0 : a_Size - 1) + 0.5f) / (float)a_Size) - 1.0f;
	// 
	// 					// Recover vector at (edge - 1)
	// 					//U contribution
	// 					VM_SCALE3(EdgeNormalU, sgFace2DMapping[a_FaceIdx][CP_UDIR], nvcUEdgeMinus1);
	// 					//V contribution
	// 					VM_SCALE3(EdgeNormalV, sgFace2DMapping[a_FaceIdx][CP_VDIR], nvcVEdgeMinus1);
	// 					VM_ADD3(EdgeNormalMinusOne, EdgeNormalV, EdgeNormalU);
	// 					//add face axis
	// 					VM_ADD3(EdgeNormalMinusOne, sgFace2DMapping[a_FaceIdx][CP_FACEAXIS], EdgeNormalMinusOne);
	// 					//normalize vector
	// 					VM_NORM3(EdgeNormalMinusOne, EdgeNormalMinusOne);
	// 
	// 					// Get angle between the two vector (which is 50% of the two vector presented in the TriAce slide)
	// 					float AngleNormalEdge = acosf(VM_DOTPROD3(EdgeNormal, EdgeNormalMinusOne));
	// 
	// 					// Here we assume that high resolution required less offset than small resolution (TriAce based this on blur radius and custom value) 
	// 					// Start to increase from 50% to 100% target angle from 128x128x6 to 1x1x6
	// 					float NumLevel = (logf(min(a_Size, 128)) / logf(2)) - 1;
	// 					AngleNormalEdge = LERP(0.5 * AngleNormalEdge, AngleNormalEdge, 1.0f - (NumLevel / 6));
	// 
	// 					float factorU = abs((2.0f * ((float)a_U) / (float)(a_Size - 1)) - 1.0f);
	// 					float factorV = abs((2.0f * ((float)a_V) / (float)(a_Size - 1)) - 1.0f);
	// 					AngleNormalEdge = LERP(0.0f, AngleNormalEdge, max(factorU, factorV));
	// 
	// 					// Get current vector
	// 					//generate x,y,z vector (xform 2d NVC coord to 3D vector)
	// 					//U contribution
	// 					VM_SCALE3(a_XYZ, sgFace2DMapping[a_FaceIdx][CP_UDIR], nvcU);
	// 					//V contribution
	// 					VM_SCALE3(tempVec, sgFace2DMapping[a_FaceIdx][CP_VDIR], nvcV);
	// 					VM_ADD3(a_XYZ, tempVec, a_XYZ);
	// 					//add face axis
	// 					VM_ADD3(a_XYZ, sgFace2DMapping[a_FaceIdx][CP_FACEAXIS], a_XYZ);
	// 					//normalize vector
	// 					VM_NORM3(a_XYZ, a_XYZ);
	// 
	// 					float RadiantAngle = AngleNormalEdge;
	// 					// Get angle between face normal and current normal. Used to push the normal away from face normal.
	// 					float AngleFaceVector = acosf(VM_DOTPROD3(sgFace2DMapping[a_FaceIdx][CP_FACEAXIS], a_XYZ));
	// 
	// 					// Push the normal away from face normal by an angle of RadiantAngle
	// 					slerp(a_XYZ, sgFace2DMapping[a_FaceIdx][CP_FACEAXIS], a_XYZ, 1.0f + RadiantAngle / AngleFaceVector);
	// 				}
	// 				else
	// 				{
	// 					//generate x,y,z vector (xform 2d NVC coord to 3D vector)
	// 					//U contribution
	// 					VM_SCALE3(a_XYZ, sgFace2DMapping[a_FaceIdx][CP_UDIR], nvcU);
	// 					//V contribution
	// 					VM_SCALE3(tempVec, sgFace2DMapping[a_FaceIdx][CP_VDIR], nvcV);
	// 					VM_ADD3(a_XYZ, tempVec, a_XYZ);
	// 					//add face axis
	// 					VM_ADD3(a_XYZ, sgFace2DMapping[a_FaceIdx][CP_FACEAXIS], a_XYZ);
	// 
	// 					//normalize vector
	// 					VM_NORM3(a_XYZ, a_XYZ);
	// 				}
}

void CCgenUtil::DetermineFilterExtents(Vec4 *centerDir, int a_SrcSize, int a_BBoxSize, SFilterExtentsLUT::FilterExtent *outFilterExtents)
{
	int u, v;
	int faceIdx;
	int minU, minV, maxU, maxV;
	int i;

	//neighboring face and bleed over amount, and width of BBOX for
	// left, right, top, and bottom edges of this face
	int bleedOverAmount[4];
	int bleedOverBBoxMin[4];
	int bleedOverBBoxMax[4];

	int neighborFace;
	int neighborEdge;

	//get face idx, and u, v info from center tap dir
	VectToTexelCoord(centerDir, a_SrcSize, &faceIdx, &u, &v);

	outFilterExtents->Clear();
	outFilterExtents->Expand(faceIdx, u - a_BBoxSize, v - a_BBoxSize);
	outFilterExtents->Expand(faceIdx, u + a_BBoxSize, v + a_BBoxSize);

	outFilterExtents->ClampMin(faceIdx, 0, 0);
	outFilterExtents->ClampMax(faceIdx, a_SrcSize - 1, a_SrcSize - 1);

	//u and v extent in face corresponding to center tap
	minU = outFilterExtents->minTexelX[faceIdx];
	minV = outFilterExtents->minTexelY[faceIdx];
	maxU = outFilterExtents->maxTexelX[faceIdx];
	maxV = outFilterExtents->maxTexelY[faceIdx];

	//bleed over amounts for face across u=0 edge (left)    
	bleedOverAmount[0] = (a_BBoxSize - u);
	bleedOverBBoxMin[0] = minV;
	bleedOverBBoxMax[0] = maxV;

	//bleed over amounts for face across u=1 edge (right)    
	bleedOverAmount[1] = (u + a_BBoxSize) - (a_SrcSize - 1);
	bleedOverBBoxMin[1] = minV;
	bleedOverBBoxMax[1] = maxV;

	//bleed over to face across v=0 edge (up)
	bleedOverAmount[2] = (a_BBoxSize - v);
	bleedOverBBoxMin[2] = minU;
	bleedOverBBoxMax[2] = maxU;

	//bleed over to face across v=1 edge (down)
	bleedOverAmount[3] = (v + a_BBoxSize) - (a_SrcSize - 1);
	bleedOverBBoxMin[3] = minU;
	bleedOverBBoxMax[3] = maxU;

	//compute bleed over regions in neighboring faces
	for (i = 0; i<4; i++)
	{
		if (bleedOverAmount[i] > 0)
		{
			neighborFace = sg_CubeNgh[faceIdx][i].m_Face;
			neighborEdge = sg_CubeNgh[faceIdx][i].m_Edge;

			//For certain types of edge abutments, the bleedOverBBoxMin, and bleedOverBBoxMax need to 
			//  be flipped: the cases are 
			// if a left   edge mates with a left or bottom  edge on the neighbor
			// if a top    edge mates with a top or right edge on the neighbor
			// if a right  edge mates with a right or top edge on the neighbor
			// if a bottom edge mates with a bottom or left  edge on the neighbor
			//Seeing as the edges are enumerated as follows 
			// left   =0 
			// right  =1 
			// top    =2 
			// bottom =3            
			// 
			// so if the edge enums are the same, or the sum of the enums == 3, 
			//  the bbox needs to be flipped
			if ((i == neighborEdge) || ((i + neighborEdge) == 3))
			{
				bleedOverBBoxMin[i] = (a_SrcSize - 1) - bleedOverBBoxMin[i];
				bleedOverBBoxMax[i] = (a_SrcSize - 1) - bleedOverBBoxMax[i];
			}


			//The way the bounding box is extended onto the neighboring face
			// depends on which edge of neighboring face abuts with this one
			switch (sg_CubeNgh[faceIdx][i].m_Edge)
			{
			case CP_EDGE_LEFT:
				outFilterExtents->Expand(neighborFace, 0, bleedOverBBoxMin[i]);
				outFilterExtents->Expand(neighborFace, bleedOverAmount[i], bleedOverBBoxMax[i]);
				break;
			case CP_EDGE_RIGHT:
				outFilterExtents->Expand(neighborFace, (a_SrcSize - 1), bleedOverBBoxMin[i]);
				outFilterExtents->Expand(neighborFace, (a_SrcSize - 1) - bleedOverAmount[i], bleedOverBBoxMax[i]);
				break;
			case CP_EDGE_TOP:
				outFilterExtents->Expand(neighborFace, bleedOverBBoxMin[i], 0);
				outFilterExtents->Expand(neighborFace, bleedOverBBoxMax[i], bleedOverAmount[i]);
				break;
			case CP_EDGE_BOTTOM:
				outFilterExtents->Expand(neighborFace, bleedOverBBoxMin[i], (a_SrcSize - 1));
				outFilterExtents->Expand(neighborFace, bleedOverBBoxMax[i], (a_SrcSize - 1) - bleedOverAmount[i]);
				break;
			}

			//clamp filter extents in non-center tap faces to remain within surface
			outFilterExtents->ClampMin(neighborFace, 0, 0);
			outFilterExtents->ClampMax(neighborFace, a_SrcSize - 1, a_SrcSize - 1);
		}

		if (bleedOverAmount[i] > a_SrcSize)
		{
			unsigned int oppositeFaceIdx;

			//determine opposite face 
			switch (faceIdx)
			{
			case CP_FACE_X_POS:
				oppositeFaceIdx = CP_FACE_X_NEG;
				break;
			case CP_FACE_X_NEG:
				oppositeFaceIdx = CP_FACE_X_POS;
				break;
			case CP_FACE_Y_POS:
				oppositeFaceIdx = CP_FACE_Y_NEG;
				break;
			case CP_FACE_Y_NEG:
				oppositeFaceIdx = CP_FACE_Y_POS;
				break;
			case CP_FACE_Z_POS:
				oppositeFaceIdx = CP_FACE_Z_NEG;
				break;
			case CP_FACE_Z_NEG:
				oppositeFaceIdx = CP_FACE_Z_POS;
				break;
			default:
				assert(false);
				break;
			}

			//just encompass entire face for now
			outFilterExtents->Expand(oppositeFaceIdx, 0, 0);
			outFilterExtents->Expand(oppositeFaceIdx, (a_SrcSize - 1), (a_SrcSize - 1));
		}
	}

	//minV = minV;
}

void CCgenUtil::VectToTexelCoord(Vec4 *vector, int a_Size, int *a_FaceIdx, int *a_U, int *a_V)
{

	float nvcU, nvcV;
	//float absXYZ[3];
	//float XYZ[3];
	float maxCoord;
	int   faceIdx;

	Vec4 absVec;

	CGen::Vec4Abs(&absVec, vector);

// 	XMStoreFloat3((Vec4*)XYZ, *vector);
// 
// 	absXYZ[0] = abs(XYZ[0]);
// 	absXYZ[1] = abs(XYZ[1]);
// 	absXYZ[2] = abs(XYZ[2]);

	if ((absVec.x >= absVec.y) && (absVec.x >= absVec.z))
	{
		maxCoord = absVec.x;

		if (vector->x >= 0) //face = XPOS
		{
			faceIdx = CP_FACE_X_POS;
		}
		else
		{
			faceIdx = CP_FACE_X_NEG;
		}
	}
	else if ((absVec.y >= absVec.x) && (absVec.y >= absVec.z))
	{
		maxCoord = absVec.y;

		if (vector->y >= 0) //face = XPOS
		{
			faceIdx = CP_FACE_Y_POS;
		}
		else
		{
			faceIdx = CP_FACE_Y_NEG;
		}
	}
	else  // if( (absXYZ[2] > absXYZ[0]) && (absXYZ[2] > absVec.y) )
	{
		maxCoord = absVec.z;

		if (vector->z >= 0) //face = XPOS
		{
			faceIdx = CP_FACE_Z_POS;
		}
		else
		{
			faceIdx = CP_FACE_Z_NEG;
		}
	}

	static const int CP_UDIR = 0;
	static const int CP_VDIR = 1;
	static const int CP_FACEAXIS = 2;

	Vec4 onFace;
	
	CGen::Vec4Scale(&onFace, vector, 1.0f / maxCoord);

	nvcU = CGen::Vec4Dotp(&onFace, &gFace2DMapping[faceIdx][CP_UDIR]);
	nvcV = CGen::Vec4Dotp(&onFace, &gFace2DMapping[faceIdx][CP_VDIR]);

	// SL BEGIN
	// Modify original AMD code to return value from 0 to Size - 1
	*a_U = (int)floor((a_Size - 1) * 0.5f * (nvcU + 1.0f));
	*a_V = (int)floor((a_Size - 1) * 0.5f * (nvcV + 1.0f));
	// SL END

	*a_FaceIdx = faceIdx;
}

float CCgenUtil::GetBaseFilterAngle(float cosinePower)
{
	const float threshold = 0.000001f; 

	float Angle = acosf(powf(threshold, 1.0f / cosinePower));
	Angle *= 180.0f / CP_PIF;
	Angle *= 2.0f;

	return Angle;
}

float CCgenUtil::TexelCoordSolidAngle(float a_U, float a_V, int a_Size)
{
	// transform from [0..res - 1] to [- (1 - 1 / res) .. (1 - 1 / res)]
	// (+ 0.5f is for texel center addressing)
	float U = (2.0f * ((float)a_U + 0.5f) / (float)a_Size) - 1.0f;
	float V = (2.0f * ((float)a_V + 0.5f) / (float)a_Size) - 1.0f;

	// Shift from a demi texel, mean 1.0f / a_Size with U and V in [-1..1]
	float InvResolution = 1.0f / a_Size;

	// U and V are the -1..1 texture coordinate on the current face.
	// Get projected area for this texel
	float x0 = U - InvResolution;
	float y0 = V - InvResolution;
	float x1 = U + InvResolution;
	float y1 = V + InvResolution;
	float SolidAngle = AreaElement(x0, y0) - AreaElement(x0, y1) - AreaElement(x1, y0) + AreaElement(x1, y1);

	return SolidAngle;
}

void CJobDispatcher::CComputeFilterExtentsLUTFunctor::operator()(SFilterState filterState)
{
	assert(filterState.GetStateID() != -1);

	const SFilterStateData *stateInstance = dispatcher->GetFilterStateDesc(&filterState);
	const SFilterStateDesc &stateDesc = stateInstance->desc;

	SFilterExtentsLUT *lut = dispatcher->GetFilterExtentsLUT(&filterState);

	lut->Init(stateDesc.dstSize, stateDesc.dstSize);

	for (unsigned int i_face = 0, i_surface = 0; i_face < 6; i_face++)
	{
		unsigned int mipWidth = stateDesc.dstSize;
		unsigned int mipHeight = stateDesc.dstSize;

		for (unsigned int i_level = 0; i_level < lut->mipCount; i_level++)
		{
			SFilterExtentsLUT::FilterExtent *dstPtr = lut->dataPtrs[i_surface++].data;

			Vec4 centerDir;

			for (unsigned int i_y = 0; i_y < mipHeight; i_y++)
			{
				for (unsigned int i_x = 0; i_x < mipWidth; i_x++)
				{
					CCgenUtil::TexelCoordToVect(i_face, (float)i_x, (float)i_y, mipWidth, centerDir, stateDesc.fixupType);
					CCgenUtil::DetermineFilterExtents(&centerDir, stateDesc.srcSize, stateInstance->filterSizes[i_level], dstPtr);
					dstPtr++;
				}
			}

			mipWidth = mipWidth >> 1;
			mipHeight = mipHeight >> 1;
		}
	}

#ifdef USE_GPU
	if(dispatcher->IsGpuEnabled())
	{
		lut->gpuDataPtrs = new STexture;
		dispatcher->gpuHandler->CreateFilterExtentLUT(lut->dataPtrs, lut->mipCount, &lut->gpuDataPtrs);
	}
#endif

	lut->status = eLUTS_ready;
}

void CJobDispatcher::CComputeTapDirLUTFunctor::operator()(SFilterState filterState)
{
	assert(filterState.GetStateID() != -1);

	const SFilterStateData *stateInstance = dispatcher->GetFilterStateDesc(&filterState);
	const SFilterStateDesc &stateDesc = stateInstance->desc;

	STapdirLUT *lut = dispatcher->GetTapDirLUT(&filterState);

	int tapDirSize = CGen::max(stateDesc.dstSize, stateDesc.srcSize);

	lut->Init(tapDirSize, tapDirSize);

	for (unsigned int i_face = 0, i_surface = 0; i_face < 6; i_face++)
	{
		unsigned int mipWidth = tapDirSize;
		unsigned int mipHeight = tapDirSize;

		for (unsigned int i_level = 0; i_level < lut->mipCount; i_level++)
		{
			Vec4 *dstPtr = lut->dataPtrs[i_surface++].data;

			for (unsigned int i_y = 0; i_y < mipHeight; i_y++)
			{
				for (unsigned int i_x = 0; i_x < mipWidth; i_x++)
				{
					CCgenUtil::TexelCoordToVect(i_face, (float)i_x, (float)i_y, mipWidth, *dstPtr, stateDesc.fixupType);	 //TODO: may use normalized UV?
					dstPtr->w = CCgenUtil::TexelCoordSolidAngle((float)i_x, (float)i_y, mipWidth);
					dstPtr++;
				}
			}

			mipWidth = mipWidth >> 1;
			mipHeight = mipHeight >> 1;
		}
	}

#ifdef USE_GPU
	if(dispatcher->gpuHandler->IsGpuEnabled())
	{
		lut->gpuDataPtrs = new STexture;
		dispatcher->gpuHandler->CreateTapdirLUT(lut->dataPtrs, lut->mipCount, &lut->gpuDataPtrs);
	}	
#endif

	lut->status = eLUTS_ready;

	return;
}

void SFilterExtentsLUT::FilterExtent::Expand(int faceId, __int16 x, __int16 y)
{
	minTexelX[faceId] = CGen::min(minTexelX[faceId], x);
	minTexelY[faceId] = CGen::min(minTexelY[faceId], y);
	maxTexelX[faceId] = CGen::max(maxTexelX[faceId], x);
	maxTexelY[faceId] = CGen::max(maxTexelY[faceId], y);
}

void SFilterExtentsLUT::FilterExtent::ClampMin(int faceId, __int16 x, __int16 y)
{
	minTexelX[faceId] = CGen::max(minTexelX[faceId], x);
	minTexelY[faceId] = CGen::max(minTexelY[faceId], y);
}

void SFilterExtentsLUT::FilterExtent::ClampMax(int faceId, __int16 x, __int16 y)
{
	maxTexelX[faceId] = CGen::min(maxTexelX[faceId], x);
	maxTexelY[faceId] = CGen::min(maxTexelY[faceId], y);
}

void SFilterExtentsLUT::Init(unsigned int width, unsigned int height)
{
	SAFE_DELETE_ARRAY(dataPtrs);

	mipCount = (unsigned int)CGen::log2((float)CGen::max(width, height)) + 1;

	dataPtrs = new TLUTData<FilterExtent>[mipCount * 6];

	for (unsigned int i_face = 0, lut = 0; i_face < 6; i_face++)
	{
		unsigned int mipWidth = width;
		unsigned int mipHeight = height;

		for (unsigned int i_mip = 0; i_mip < mipCount; i_mip++)
		{
			dataPtrs[lut++].Allocate(mipWidth, mipHeight);

			mipWidth = mipWidth >> 1;
			mipHeight = mipHeight >> 1;
		}
	}
}

void CJobDispatcher::CMatchLUTFunctor::operator()(SFilterJobInternal *filterJob)
{
	SFilterJob &job = filterJob->job;

	const STapdirLUT *lut = dispatcher->GetTapDirLUT(job.filterState);
	const SFilterExtentsLUT *feLut = dispatcher->GetFilterExtentsLUT(job.filterState);

	bool readyToFilter = true;

	if (lut->status != eLUTS_ready)
	{
		readyToFilter = false;
		dispatcher->ComputeTapDirLUT(job.filterState);
	}

	if (feLut->status != eLUTS_ready)
	{
		readyToFilter = false;
		dispatcher->ComputeFilterExtentsLUT(job.filterState);
	}

	if (filterJob->vectorCubemap->internalStatus != eLUTS_ready)
	{
		readyToFilter = false;
		dispatcher->VectorizeCubemap(filterJob);
	}

	if(dispatcher->IsGpuEnabled())
	{
		assert(lut->status == eLUTS_ready);
		assert(feLut->status == eLUTS_ready);
		assert(filterJob->vectorCubemap->internalStatus == eLUTS_ready);

		readyToFilter = true;
	}

	if (readyToFilter)
	{
		printf("Filtering started\n");

#ifdef USE_GPU
		if(dispatcher->gpuHandler->IsGpuEnabled())
		{
			SCubeFace inputFaces[6];

			for (int i_inputFace = 0; i_inputFace < 6; i_inputFace++)
			{
				inputFaces[i_inputFace] = filterJob->vectorCubemap->faces[i_inputFace * job.inputCubemap->mipCount];
			}

			SGpuFilterCubemapJob *newJob = new SGpuFilterCubemapJob;

			newJob->jobCount = (*job.outputCubemap)->mipCount * 6;
			newJob->filterJobs = new SGpuFilterLevelJob[newJob->jobCount];
			newJob->parentJob = filterJob;
			newJob->filterState = job.filterState;
			newJob->filterStateData = dispatcher->GetFilterStateDesc(job.filterState);
			memcpy(newJob->inputFaces, inputFaces, sizeof(SCubeFace)* 6);			

			SFilterExtentsLUT *filterExtentsLut = dispatcher->GetFilterExtentsLUT(job.filterState);
			STapdirLUT *tapDirLut = dispatcher->GetTapDirLUT(job.filterState);
			const SFilterStateData *stateData = dispatcher->GetFilterStateDesc(job.filterState);
			for (int i_face = 0, i_job = 0; i_face < 6; i_face++)
			{
				for (unsigned int i_level = 0; i_level < (*job.outputCubemap)->mipCount; i_level++)
				{
					SGpuFilterLevelJob *curJob = &newJob->filterJobs[i_job];
		
					curJob->job.outputCubeFace = (*filterJob->job.outputCubemap)->faces[i_face * (*job.outputCubemap)->mipCount + i_level];
					memcpy(curJob->job.inputCubeFaces, inputFaces, sizeof(SCubeFace)* 6);
					curJob->job.filterState = job.filterState;
					curJob->job.outputFaceIndex = i_face;
					curJob->job.filterStateData = stateData;
					curJob->tapdirLut = tapDirLut;
					curJob->filterExtentsLut = filterExtentsLut;
					i_job++;
				}
			}

			SGpuThreadMessage msg;

			msg.code = eGMC_filter_cubemap;
			msg.data = newJob;

			dispatcher->gpuHandler->SendMessage(msg);
		}
		else
#endif
		{
			for (unsigned int i_level = 0; i_level < (*job.outputCubemap)->mipCount; i_level++)
			{
				SCubeFace inputFaces[6];

				for (int i_inputFace = 0; i_inputFace < 6; i_inputFace++)
				{
					if (false)
					{
						inputFaces[i_inputFace] = filterJob->vectorCubemap->faces[i_inputFace * job.inputCubemap->mipCount + i_level];
					}
					else
					{
						inputFaces[i_inputFace] = filterJob->vectorCubemap->faces[i_inputFace * job.inputCubemap->mipCount];
					}
				}		

				for (int i_face = 0; i_face < 6; i_face++)
				{
					SFilterSurfaceJob levelJob;

					levelJob.outputCubeFace = (*filterJob->job.outputCubemap)->faces[i_face * (*job.outputCubemap)->mipCount + i_level];
					memcpy(levelJob.inputCubeFaces, inputFaces, sizeof(SCubeFace)* 6);
					levelJob.filterState = job.filterState;
					levelJob.outputFaceIndex = i_face;
					levelJob.filterStateData = dispatcher->GetFilterStateDesc(job.filterState);
					levelJob.parentJob = filterJob;
					dispatcher->DispatchFilterSurfaceJob(&levelJob);
				}
			}
		}
	}
	else
	{
		dispatcher->filterJobBufferNode->try_put(filterJob);
	}
}

struct SPixelFormat_R8G8B8A8
{
	unsigned __int8 r, g, b, a;
};

void CJobDispatcher::CProcessFilterJobFunctor::operator()(SFilterSurfaceJob filterLevelJob)
{
	unsigned int opWidth = filterLevelJob.outputCubeFace.width;
	unsigned int opHeight = filterLevelJob.outputCubeFace.height;

	const SFilterStateData *stateInstance = dispatcher->GetFilterStateDesc(filterLevelJob.filterState);
	const SFilterStateDesc &stateDesc = stateInstance->desc;
	const STapdirLUT *tapDirLut = dispatcher->GetTapDirLUT(filterLevelJob.filterState);
	const SFilterExtentsLUT *feLut = dispatcher->GetFilterExtentsLUT(filterLevelJob.filterState);

	unsigned int dstTapDirMipIndex = tapDirLut->mipCount - (int)(CGen::log2((float)filterLevelJob.outputCubeFace.width) + 1);
	unsigned int dstTapDirSurfaceIndex = dstTapDirMipIndex + tapDirLut->mipCount * filterLevelJob.outputFaceIndex;
	unsigned int srcTapDirMipIndex = tapDirLut->mipCount - (int)(CGen::log2((float)filterLevelJob.inputCubeFaces->width) + 1);
	unsigned int feMipIndex = feLut->mipCount - (int)(CGen::log2((float)filterLevelJob.outputCubeFace.width) + 1);
	unsigned int feSurfaceIndex = feMipIndex + feLut->mipCount * filterLevelJob.outputFaceIndex;

	unsigned int dstCubemapMipIndex = (int)CGen::log2((float)stateDesc.dstSize) - (int)CGen::log2((float)filterLevelJob.outputCubeFace.width); //TODO divide logs ?

	unsigned int srcTapDirSurfaceIndexes[6];
	
	for (int i_face = 0; i_face < 6; i_face++)
	{
		srcTapDirSurfaceIndexes[i_face] = srcTapDirMipIndex + tapDirLut->mipCount * i_face;
	}
	
	assert(tapDirLut->mipCount * 6 > dstTapDirSurfaceIndex);

	SFilterExtentsLUT::FilterExtent * fePtr = feLut->dataPtrs[feSurfaceIndex].data;
	Vec4 * const tapdirPtr = tapDirLut->dataPtrs[dstTapDirSurfaceIndex].data;
	Vec4 zero(0.0f);

	int uStart, uEnd;
	int vStart, vEnd;

	Vec4 *mainTapDir = tapdirPtr;
	Vec4 dotpThreshold(stateInstance->dotpThreshold[dstCubemapMipIndex]);

	float IsPhongBRDF = (stateDesc.lightingModel == CP_LIGHTINGMODEL_PHONG_BRDF || stateDesc.lightingModel == CP_LIGHTINGMODEL_BLINN_BRDF) ? 1.0f : 0;

	Vec4 powValue(stateInstance->specularPowers[dstCubemapMipIndex] + IsPhongBRDF);
	Vec4 destAccum;
	float weightAccum;
	float *powTable = (dispatcher->powTables[filterLevelJob.filterState->GetStateID()])[dstCubemapMipIndex];

	unsigned __int8 *dstPtr = (unsigned __int8 *)filterLevelJob.outputCubeFace.dataPtr;

	CGen::max(dstCubemapMipIndex, 1U);

	for (unsigned int i_x = 0; i_x < opWidth; i_x++)
	{
		for (unsigned int i_y = 0; i_y < opWidth; i_y++)
		{
			destAccum = CGen::Vec4Zero();
			weightAccum = 0;

			for (int iFaceIdx = 0; iFaceIdx < 6; iFaceIdx++)
			{
				if (fePtr->IsEmpty(iFaceIdx) == FALSE)
				{
					uStart = fePtr->minTexelX[iFaceIdx];
					vStart = fePtr->minTexelY[iFaceIdx];
					uEnd = fePtr->maxTexelX[iFaceIdx];
					vEnd = fePtr->maxTexelY[iFaceIdx];

					Vec4 *texelTapdirPtr = tapDirLut->dataPtrs[srcTapDirSurfaceIndexes[iFaceIdx]].GetPtr(uStart, vStart);
					Vec4 *srcPixelPtr = ((Vec4*)(filterLevelJob.inputCubeFaces[iFaceIdx].dataPtr)) + (vStart * filterLevelJob.inputCubeFaces[iFaceIdx].width + uStart); // :/
					Vec4 *tapdirRowStart = texelTapdirPtr;
					Vec4 *srcRowStart = srcPixelPtr;

					//note that <= is used to ensure filter extents always encompass at least one pixel if bbox is non empty
					for (int v = vStart; v <= vEnd; v += 1)
					{
						Vec4 *tapDir = tapdirRowStart;
						Vec4 *srcPtr = srcRowStart;

						for (int u = uStart; u <= uEnd; u += 1)
						{
							float dotpFloat = CGen::Vec4Dotp(mainTapDir, tapDir);

							if (dotpFloat >= stateInstance->dotpThreshold[dstCubemapMipIndex] && dotpFloat > 0.0f)
							{
								float weight = tapDir->w * powTable[(int)(dotpFloat * (16 * 1024 - 1))];
								Vec4 weighted_src;
								CGen::Vec4Scale(&weighted_src, srcPtr, weight);
								CGen::Vec4Add(&destAccum, &weighted_src, &destAccum);
								weightAccum += weight;
							}

							srcPtr += 1;
							tapDir += 1;
						}

						srcRowStart += filterLevelJob.inputCubeFaces->width;
						tapdirRowStart += tapDirLut->dataPtrs[srcTapDirSurfaceIndexes[iFaceIdx]].width;
					}
				}
			}

			if (weightAccum > 0)
			{
				CGen::Vec4Scale(&destAccum, &destAccum, 1.0f / weightAccum);
			}
			else
			{
				destAccum = Vec4(1.0f);
			}

			dstPtr[0] = (unsigned char)(CGen::min(1.0f, CGen::max(0.0f, destAccum.x)) * 255.0f);
			dstPtr[1] = (unsigned char)(CGen::min(1.0f, CGen::max(0.0f, destAccum.y)) * 255.0f);
			dstPtr[2] = (unsigned char)(CGen::min(1.0f, CGen::max(0.0f, destAccum.z)) * 255.0f);
			dstPtr[3] = 0xFF;
			dstPtr += 4;
			fePtr++;
			mainTapDir++;
		}
	}

	InterlockedDecrement(&filterLevelJob.parentJob->internalCounter);

	if(filterLevelJob.parentJob->internalCounter == 0)
	{
		dispatcher->RemoveActiveJobAndNotify(filterLevelJob.parentJob);
		InterlockedIncrement(&dispatcher->processedJobCount);
	}

	printf("completed %d", filterLevelJob.outputCubeFace.width);
}

void CJobDispatcher::CVectorizeCubemapFunctor::operator()(SFilterJobInternal *filterJob)
{
	SCubeMap *vectorCubemap = filterJob->vectorCubemap;

	SCubeMap *inputCubemap = filterJob->job.inputCubemap;

	EPixelFormat targetFormat = dispatcher->IsGpuEnabled() ? ePF_R16G16B16A16F : ePF_VECTOR;


	vectorCubemap->pixelFormat = targetFormat;
	vectorCubemap->width = inputCubemap->width;
	vectorCubemap->height = inputCubemap->height;
	vectorCubemap->mipCount = inputCubemap->mipCount;
	vectorCubemap->faces = new SCubeFace[inputCubemap->mipCount * 6];
	
	for (unsigned int i_face = 0, i_surface = 0; i_face < 6; i_face++)
	{
		unsigned int mipWidth = vectorCubemap->width;
		unsigned int mipHeight = vectorCubemap->height;

		for (unsigned int i_mip = 0; i_mip < vectorCubemap->mipCount; i_mip++)
		{
			if (targetFormat == ePF_R16G16B16A16F)
			{
				SCubeFace &curFace = vectorCubemap->faces[i_surface];

				unsigned int surfaceByteWidth = mipWidth * mipHeight * sizeof(__int16) * 4;

				curFace.dataPtr = _aligned_malloc(surfaceByteWidth, 16);
				curFace.dataSize = surfaceByteWidth;
				curFace.width = mipWidth;
				curFace.height = mipHeight;

				if(inputCubemap->pixelFormat == ePF_R16G16B16A16F)
				{
					assert(surfaceByteWidth == inputCubemap->faces[i_surface].dataSize);
					memcpy_s(curFace.dataPtr, surfaceByteWidth, inputCubemap->faces[i_surface].dataPtr, inputCubemap->faces[i_surface].dataSize);
				}
				else if (inputCubemap->pixelFormat == ePF_VECTOR)
				{
					Vec4 *srcPtr = (Vec4*)inputCubemap->faces[i_surface].dataPtr;
					unsigned __int16 *dstPtr = (unsigned __int16 *)vectorCubemap->faces[i_surface].dataPtr;
					
					for (unsigned int i_x = 0; i_x < mipWidth; i_x++)
					{
						for (unsigned int i_y = 0; i_y < mipHeight; i_y++)
						{
							dstPtr[0] = half_float::detail::float2half<std::round_to_nearest>(srcPtr->x);
							dstPtr[1] = half_float::detail::float2half<std::round_to_nearest>(srcPtr->y);
							dstPtr[2] = half_float::detail::float2half<std::round_to_nearest>(srcPtr->z);
							dstPtr[3] = half_float::detail::float2half<std::round_to_nearest>(255.0f);
							//*dstPtr = XMVectorSet(srcPtr[0] / 255.0f, srcPtr[1] / 255.0f, srcPtr[2] / 255.0f, 1.0f);
							srcPtr++;
							dstPtr += 4;
						}
					}
				}
				else if (inputCubemap->pixelFormat == ePF_R8G8BA8)
				{
					unsigned __int8 *srcPtr = (unsigned __int8 *)inputCubemap->faces[i_surface].dataPtr;
					unsigned __int16 *dstPtr = (unsigned __int16 *)vectorCubemap->faces[i_surface].dataPtr;
					
					for (unsigned int i_x = 0; i_x < mipWidth; i_x++)
					{
						for (unsigned int i_y = 0; i_y < mipHeight; i_y++)
						{
							dstPtr[0] = half_float::detail::float2half<std::round_to_nearest>(srcPtr[0] * 255.0f);
							dstPtr[1] = half_float::detail::float2half<std::round_to_nearest>(srcPtr[1] * 255.0f);
							dstPtr[2] = half_float::detail::float2half<std::round_to_nearest>(srcPtr[2] * 255.0f);
							dstPtr[3] = half_float::detail::float2half<std::round_to_nearest>(255.0f);
							//*dstPtr = XMVectorSet(srcPtr[0] / 255.0f, srcPtr[1] / 255.0f, srcPtr[2] / 255.0f, 1.0f);
							srcPtr ++;
							dstPtr += 4;
						}
					}
				}
				else
				{
					assert(false);
				}
			}
			else if (targetFormat == ePF_VECTOR)
			{
				SCubeFace &curFace = vectorCubemap->faces[i_surface];

				unsigned int surfaceByteWidth = mipWidth * mipHeight * sizeof(float) * 4;

				curFace.dataPtr = _aligned_malloc(surfaceByteWidth, 16);
				curFace.dataSize = surfaceByteWidth;
				curFace.width = mipWidth;
				curFace.height = mipHeight;

				if (inputCubemap->pixelFormat == ePF_VECTOR)
				{
					assert(surfaceByteWidth == inputCubemap->faces[i_surface].dataSize);
					memcpy_s(curFace.dataPtr, surfaceByteWidth, inputCubemap->faces[i_surface].dataPtr, inputCubemap->faces[i_surface].dataSize);
				}
				else if (inputCubemap->pixelFormat == ePF_R8G8BA8)
				{

					unsigned __int8 *srcPtr = (unsigned __int8 *)inputCubemap->faces[i_surface].dataPtr;
					Vec4 *dstPtr = (Vec4*)vectorCubemap->faces[i_surface].dataPtr;
					
					for (unsigned int i_x = 0; i_x < mipWidth; i_x++)
					{
						for (unsigned int i_y = 0; i_y < mipHeight; i_y++)
						{
							dstPtr[0].x = srcPtr[0] / 255.0f;
							dstPtr[0].y = srcPtr[1] / 255.0f;
							dstPtr[0].z = srcPtr[2] / 255.0f;
							dstPtr[0].w = 1.0f;
							//*dstPtr = XMVectorSet(srcPtr[0] / 255.0f, srcPtr[1] / 255.0f, srcPtr[2] / 255.0f, 1.0f);
							srcPtr += 4;
							dstPtr++;
						}
					}
				}
				else if(inputCubemap->pixelFormat == ePF_R16G16B16A16F)
				{
					unsigned __int16 *srcPtr = (unsigned __int16 *)inputCubemap->faces[i_surface].dataPtr;
					Vec4 *dstPtr = (Vec4*)vectorCubemap->faces[i_surface].dataPtr;
					
					for (unsigned int i_x = 0; i_x < mipWidth; i_x++)
					{
						for (unsigned int i_y = 0; i_y < mipHeight; i_y++)
						{							
							dstPtr[0].x = half_float::detail::half2float(srcPtr[0]);
							dstPtr[0].y = half_float::detail::half2float(srcPtr[1]);
							dstPtr[0].z = half_float::detail::half2float(srcPtr[2]);
							dstPtr[0].w = 1.0f;
							//*dstPtr = XMVectorSet(srcPtr[0] / 255.0f, srcPtr[1] / 255.0f, srcPtr[2] / 255.0f, 1.0f);
							srcPtr += 4;
							dstPtr++;
						}
					}
				}
				else
				{
					assert(false);
				}
			}

			assert(mipWidth > 0 && mipHeight > 0);

			mipHeight = mipHeight >> 1;
			mipWidth = mipWidth >> 1;
			i_surface++;
		}
	}

	assert(vectorCubemap->internalStatus == eLUTS_in_progress);

	vectorCubemap->internalStatus = eLUTS_ready;
}

void SCubeFace::Clear()
{
	SAFE_ALIGNED_DELETE(dataPtr);
}

int epf_pixel_size(EPixelFormat format)
{
	switch (format)
	{
	case ePF_R8G8BA8:
	case ePF_R8G8BA8_UINT:
		return 4;
	case ePF_R16G16B16A16F:
		return 8;
	case ePF_VECTOR:
		return 16;
	default:
		return 0;
	}
}